{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在 C++ 中，一旦你需要为类手写某些特殊成员函数，就往往需要成套地考虑它们，以避免资源管理或语义不一致问题。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- **三法则**：一旦手写析构/拷贝构造/拷贝赋值之一，就考虑全部定义。\n",
    "- **五法则**：在 C++11 及以后，加入移动构造/移动赋值的考量。\n",
    "- **零法则**：善用 RAII 容器和智能指针，让编译器默认生成的特殊成员函数保持正确性。\n",
    "- 无参构造函数是特殊成员函数，但与三/五法则关注的资源复制与释放问题关联较小，所以常被单独看待; 有参构造函数 ≠ 特殊成员函数"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| 法则                    | 涉及的特殊成员函数                                           | 适用场景与要点                                               | 典型做法                                                     |\n",
    "| ----------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |\n",
    "| Rule of Three（三法则） | `~Class()`、`Class(const Class&)`、`Class& operator=(const Class&)` | 当类手写析构函数（或拷贝构造/拷贝赋值）来管理资源时，应同时定义其余两者，防止浅拷贝、重复释放等问题。 | 显式实现三者，确保资源复制与释放语义一致。                   |\n",
    "| Rule of Five（五法则）  | Rule of Three 中的三个 + `Class(Class&&)`、`Class& operator=(Class&&)` | C++11及以后，如果类需要自定义资源管理，还需处理移动语义；定义任一特殊成员时要评估是否也要定义其他四个。 | 同时提供拷贝与移动的构造/赋值，确保资源既能安全复制，也能高效移动。 |\n",
    "| Rule of Zero（零法则）  | （无需手写任何特殊成员函数）                                 | 使用 RAII 容器（`std::vector`、`std::string`、`std::unique_ptr` 等）托管资源，让编译器生成的默认特殊成员函数保持正确性。 | 避免自管理资源，借助标准库类型自动获得正确的拷贝/移动/析构行为。 |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 违反“三法则”的例子（危险）"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "class BadString {\n",
    "public:\n",
    "    BadString(const char* s) {\n",
    "        size_ = std::strlen(s);\n",
    "        data_ = new char[size_ + 1];\n",
    "        std::strcpy(data_, s);\n",
    "    }\n",
    "\n",
    "    ~BadString() {\n",
    "        delete[] data_;\n",
    "    }\n",
    "\n",
    "    // 忘记定义拷贝构造/赋值 ——> 默认生成的浅拷贝会导致双重 delete\n",
    "private:\n",
    "    char* data_ = nullptr;\n",
    "    std::size_t size_ = 0;\n",
    "};"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 遵守“五法则”的资源持有类"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "class UniqueBuffer {\n",
    "public:\n",
    "    explicit UniqueBuffer(std::size_t n)\n",
    "        : size_(n), data_(new char[n]) {}\n",
    "\n",
    "    // 析构\n",
    "    ~UniqueBuffer() {\n",
    "        delete[] data_;\n",
    "    }\n",
    "\n",
    "    // 拷贝构造\n",
    "    UniqueBuffer(const UniqueBuffer& other)\n",
    "        : size_(other.size_), data_(new char[other.size_]) {\n",
    "        std::copy(other.data_, other.data_ + size_, data_);\n",
    "    }\n",
    "\n",
    "    // 拷贝赋值\n",
    "    UniqueBuffer& operator=(const UniqueBuffer& other) {\n",
    "        if (this != &other) {\n",
    "            UniqueBuffer temp(other);        // 拷贝构造临时对象\n",
    "            swap(temp);                      // 与临时对象交换\n",
    "        }\n",
    "        return *this;\n",
    "    }\n",
    "\n",
    "    // 移动构造\n",
    "    UniqueBuffer(UniqueBuffer&& other) noexcept\n",
    "        : size_(other.size_), data_(other.data_) {\n",
    "        other.size_ = 0;\n",
    "        other.data_ = nullptr;\n",
    "    }\n",
    "\n",
    "    // 移动赋值\n",
    "    UniqueBuffer& operator=(UniqueBuffer&& other) noexcept {\n",
    "        if (this != &other) {\n",
    "            delete[] data_;\n",
    "            size_ = other.size_;\n",
    "            data_ = other.data_;\n",
    "            other.size_ = 0;\n",
    "            other.data_ = nullptr;\n",
    "        }\n",
    "        return *this;\n",
    "    }\n",
    "\n",
    "    void swap(UniqueBuffer& other) noexcept {\n",
    "        std::swap(size_, other.size_);\n",
    "        std::swap(data_, other.data_);\n",
    "    }\n",
    "\n",
    "private:\n",
    "    std::size_t size_;\n",
    "    char* data_;\n",
    "};"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 遵守“零法则”的现代写法"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "#include <vector>\n",
    "#include <cstddef>\n",
    "\n",
    "class Buffer {\n",
    "public:\n",
    "    explicit Buffer(std::size_t n)\n",
    "        : data_(n) {}\n",
    "\n",
    "    // 不手写任何特殊成员\n",
    "    // std::vector 会自动处理拷贝/移动/析构\n",
    "private:\n",
    "    std::vector<char> data_;\n",
    "};"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 基类析构函数就必须是虚的"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在含有多态基类的层次结构里，“虚析构函数”几乎是铁律；原因在于 **以基类指针（或引用）操作派生类对象时，析构过程必须具有动态绑定**。如果基类的析构函数不是虚的，通过基类指针 `delete` 派生对象就会产生未定义行为。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**问题根源：静态绑定导致“只析构了基类部分”**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "struct Base {\n",
    "    ~Base() { std::puts(\"~Base\"); }\n",
    "};\n",
    "\n",
    "struct Derived : Base {\n",
    "    ~Derived() { std::puts(\"~Derived\"); }\n",
    "};\n",
    "\n",
    "Base *p = new Derived{};\n",
    "delete p;   // 若 ~Base 不是虚的，只调用 ~Base()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 当 `~Base()` 不是虚函数时，`delete p` 使用的是静态类型 `Base*`，只会调用 `Base` 的析构函数。\n",
    "- `Derived` 部分从未被销毁：资源（堆内存、文件句柄、锁等）泄漏，甚至可能访问已释放内存，属于 **未定义行为**。\n",
    "- C++ 标准明确：如果通过基类指针删除动态分配的派生对象，而基类的析构函数非虚，则行为未定义。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**解决方案：把基类析构函数声明为 `virtual`**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "struct Base {\n",
    "    virtual ~Base() { std::puts(\"~Base\"); }\n",
    "};\n",
    "\n",
    "struct Derived : Base {\n",
    "    ~Derived() override { std::puts(\"~Derived\"); }\n",
    "};"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- `delete p` 会先调用 `Derived::~Derived()`，然后自动回退到 `Base::~Base()`，析构顺序正确。\n",
    "- 这样就实现了 **动态结合（dynamic dispatch）**，保证派生对象的资源都能按预期释放。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**何时不需要虚析构？**\n",
    "\n",
    "只有在**确定不会**通过基类指针/引用释放派生对象时，才可以不虚。典型场景：\n",
    "\n",
    "1. **没有多态**：基类没有任何虚函数，设计上就是“值语义”或“非多态”类型。\n",
    "2. **抽象概念外部不负责销毁**：例如类里是接口，实际对象生命周期由别的机制（智能指针、自定义工厂）掌控，而且保障不会通过裸基类指针 `delete`。\n",
    "\n",
    "> 实际项目中，为降低维护风险和踩坑概率，凡是打算“让类型当多态基类使用”的，一律把析构函数设为 `virtual` 或 `default`。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**补充小贴士**\n",
    "\n",
    "- **纯虚析构**：可以写成 `virtual ~Base() = 0;`，但必须给出定义（哪怕是空的），否则链接错误。这常用在抽象基类里。\n",
    "- **性能影响**：虚函数表本来就存在（只要有一个虚函数）。将析构函数设为虚，对象大小不变，运行时开销仅在于一次虚调用（几乎可以忽略）。\n",
    "- **智能指针**：`std::unique_ptr<Base>` 用普通 deleter 时也要求 `Base` 有虚析构，否则 `unique_ptr` 删除派生对象同样 UB。若基类析构非虚，只能配自定义 deleter。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**总结**\n",
    "\n",
    "- **要点**：只要允许“通过基类指针/引用管理派生对象”，基类析构函数就必须是虚的。\n",
    "- **目的**：确保删除派生对象时先调用派生析构，再回溯至基类，避免资源泄漏或未定义行为。\n",
    "- **例外**：仅在明确不发生多态删除时可不虚；但通常建议显式写成 `virtual ~Base() = default;`，表达意图并保障安全。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "C++17",
   "language": "C++17",
   "name": "xcpp17"
  },
  "language_info": {
   "name": "C++17"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
